Skip to content

14 logging日志系统

写代码的时候,print()能帮你调试,但到了生产环境,print()就不够用了——你不知道什么时候发生的错误、错误有多严重、错误发生在哪个文件哪一行。logging模块就是干这个的,它是Python内置的日志系统。

一、快速开始

1.1 基本用法

python
import logging

# 基础配置
logging.basicConfig(level=logging.INFO)

# 记录日志
logging.debug("调试信息")      # 不会输出(级别太低)
logging.info("普通信息")       # 输出
logging.warning("警告信息")    # 输出
logging.error("错误信息")      # 输出
logging.critical("严重错误")   # 输出

输出格式:

INFO:root:普通信息
WARNING:root:警告信息
ERROR:root:错误信息
CRITICAL:root:严重错误

1.2 日志级别

从低到高5个标准级别:

级别数值用途
DEBUG10调试信息,详细的技术细节
INFO20普通信息,程序正常运行的记录
WARNING30警告,不影响运行但需要注意
ERROR40错误,某些功能无法执行
CRITICAL50严重错误,程序可能无法继续运行

设置某个级别后,只会记录该级别及以上的日志。

二、basicConfig配置

2.1 常用参数

python
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    filename="app.log",
    filemode="w",
    encoding="utf-8"
)

logging.info("程序启动")

输出到文件app.log

2026-06-13 10:30:00 - root - INFO - 程序启动

2.2 格式化变量

变量含义
%(asctime)s时间
%(name)sLogger名称
%(levelname)s日志级别名
%(levelno)s日志级别数值
%(message)s日志内容
%(filename)s文件名
%(lineno)d行号
%(funcName)s函数名
%(pathname)s完整路径
%(process)d进程ID
%(thread)d线程ID

2.3 同时输出到控制台和文件

python
import logging

# basicConfig只能配置一次,想同时输出到控制台和文件需要用Handler
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

# 控制台Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 文件Handler
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)

# 格式化器
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

logger.addHandler(console_handler)
logger.addHandler(file_handler)

logging.info("这条日志会同时出现在控制台和文件")

三、Logger对象

3.1 创建Logger

python
import logging

# 创建具名Logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# 添加Handler
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(name)s - %(message)s"))
logger.addHandler(handler)

logger.info("来自my_module的信息")
# 输出: my_module - 来自my_module的信息

3.2 Logger层次

.分隔的Logger名称会形成父子关系:

python
import logging

# 父Logger
parent_logger = logging.getLogger("myapp")

# 子Logger
child_logger = logging.getLogger("myapp.database")

# 子Logger会把日志传播给父Logger
child_logger.info("数据库连接成功")
# 会同时被child_logger和parent_logger的Handler处理

3.3 常用方法

python
import logging

logger = logging.getLogger(__name__)

logger.debug("调试")       # 10
logger.info("信息")        # 20
logger.warning("警告")     # 30
logger.error("错误")       # 40
logger.critical("严重")    # 50

# 记录异常信息(包含堆栈)
try:
    1 / 0
except Exception:
    logger.exception("发生异常")
# 会自动记录完整的异常堆栈

# 设置级别
logger.setLevel(logging.DEBUG)

# 检查级别
logger.isEnabledFor(logging.DEBUG)  # True

四、Handler

4.1 常用Handler

Handler用途
StreamHandler输出到流(默认stderr)
FileHandler输出到文件
RotatingFileHandler按大小轮转文件
TimedRotatingFileHandler按时间轮转文件
NullHandler丢弃所有日志(用于库)

4.2 RotatingFileHandler

按文件大小轮转,避免日志文件无限增大。

python
import logging
from logging.handlers import RotatingFileHandler

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

handler = RotatingFileHandler(
    "app.log",
    maxBytes=1024 * 1024,  # 1MB
    backupCount=5,          # 保留5个备份
    encoding="utf-8"
)
logger.addHandler(handler)

app.log达到1MB时,会自动轮转为app.log.1app.log.2等。

4.3 TimedRotatingFileHandler

按时间轮转。

python
import logging
from logging.handlers import TimedRotatingFileHandler

handler = TimedRotatingFileHandler(
    "app.log",
    when="midnight",    # 每天午夜轮转
    interval=1,
    backupCount=30,     # 保留30天
    encoding="utf-8"
)

when参数:'S'(秒)、'M'(分)、'H'(时)、'D'(天)、'midnight'(午夜)、'W0'-'W6'(星期几)。

五、Formatter

5.1 自定义格式

python
import logging

formatter = logging.Formatter(
    fmt="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

handler = logging.StreamHandler()
handler.setFormatter(formatter)

5.2 不同Handler不同格式

python
import logging

logger = logging.getLogger(__name__)

# 控制台:简洁格式
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s"))

# 文件:详细格式
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setFormatter(logging.Formatter(
    "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s"
))

logger.addHandler(console_handler)
logger.addHandler(file_handler)

六、Filter

6.1 过滤日志

python
import logging

class NoDebugFilter(logging.Filter):
    def filter(self, record):
        return record.levelno > logging.DEBUG

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler()
handler.addFilter(NoDebugFilter())
logger.addHandler(handler)

logger.debug("这条不会输出")
logger.info("这条会输出")

七、实战配置

7.1 项目日志配置

python
import logging
import logging.config

LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {
            "format": "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s"
        },
        "simple": {
            "format": "%(levelname)s - %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "simple",
            "stream": "ext://sys.stdout"
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "DEBUG",
            "formatter": "standard",
            "filename": "app.log",
            "maxBytes": 10485760,  # 10MB
            "backupCount": 5,
            "encoding": "utf-8"
        }
    },
    "root": {
        "level": "DEBUG",
        "handlers": ["console", "file"]
    }
}

logging.config.dictConfig(LOGGING_CONFIG)

7.2 模块化使用

python
# database.py
import logging

logger = logging.getLogger(__name__)

def connect():
    logger.info("连接数据库")
    # ...
    logger.debug("连接成功")

# main.py
import logging
import database

logging.basicConfig(level=logging.INFO)
database.connect()

八、常见问题

8.1 重复日志

python
# 错误:多次调用basicConfig或addHandler会导致重复日志
logging.basicConfig()
logging.basicConfig()  # 不会生效,但也不会报错

# 正确:只配置一次
# 或者检查是否已有Handler
logger = logging.getLogger(__name__)
if not logger.handlers:
    logger.addHandler(handler)

8.2 日志不输出

python
# 原因1:级别设置太高
logger.setLevel(logging.WARNING)
logger.info("不会输出")  # INFO < WARNING

# 原因2:没有Handler
logger = logging.getLogger("test")
logger.info("不会输出")  # 没有Handler

# 原因3:propagate被设为False
logger.propagate = False

九、总结

logging模块的核心:

组件作用
Logger记录日志的对象
Handler决定日志输出到哪里
Formatter决定日志的格式
Filter过滤日志

常用模式:

python
import logging

# 快速配置
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")

# 使用
logging.info("程序启动")

# 或者用具名Logger
logger = logging.getLogger(__name__)
logger.info("模块加载完成")

记住basicConfig()配置一次,然后到处用logging.info()就够了。生产环境再加上文件Handler和轮转。